/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.tst;

import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.search.*;
import org.apache.lucene.util.OpenBitSet;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @version $Id: TestRequestHandler.java 1229268 2012-01-09 17:31:15Z mikemccand
 *          $
 * 
 * @deprecated Test against the real request handlers instead.
 */
@Deprecated
public class TestRequestHandler implements SolrRequestHandler {
	private static Logger log = LoggerFactory
			.getLogger(SolrIndexSearcher.class);

	public void init(NamedList args) {
		SolrCore.log.info("Unused request handler arguments:" + args);
	}

	// use test instead of assert since asserts may be turned off
	public void test(boolean condition) {
		try {
			if (!condition) {
				throw new RuntimeException(
						"test requestHandler: assertion failed!");
			}
		} catch (RuntimeException e) {
			SolrException.log(log, e);
			throw (e);
		}
	}

	private long numRequests;
	private long numErrors;

	private final Pattern splitList = Pattern.compile(",| ");

	public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) {
		numRequests++;

		// TODO: test if lucene will accept an escaped ';', otherwise
		// we need to un-escape them before we pass to QueryParser
		try {
			String sreq = req.getQueryString();
			if (sreq == null)
				throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
						"Missing queryString");
			List<String> commands = StrUtils.splitSmart(sreq, ';');

			String qs = commands.size() >= 1 ? commands.get(0) : "";
			Query query = QueryParsing.parseQuery(qs, req.getSchema());

			// find fieldnames to return (fieldlist)
			String fl = req.getParam("fl");
			int flags = 0;
			if (fl != null) {
				// TODO - this could become more efficient if widely used.
				// TODO - should field order be maintained?
				String[] flst = splitList.split(fl, 0);
				if (flst.length > 0
						&& !(flst.length == 1 && flst[0].length() == 0)) {
					Set<String> set = new HashSet<String>();
					for (String fname : flst) {
						if ("score".equals(fname))
							flags |= SolrIndexSearcher.GET_SCORES;
						set.add(fname);
					}
					rsp.setReturnFields(set);
				}
			}

			// If the first non-query, non-filter command is a simple sort on an
			// indexed field, then
			// we can use the Lucene sort ability.
			Sort sort = null;
			if (commands.size() >= 2) {
				sort = QueryParsing.parseSort(commands.get(1), req);
			}

			SolrIndexSearcher searcher = req.getSearcher();

			/***
			 * Object o = searcher.cacheLookup("dfllNode", query); if (o ==
			 * null) { searcher.cacheInsert("dfllNode",query,"Hello Bob"); }
			 * else { System.out.println("User Cache Hit On " + o); }
			 ***/

			int start = req.getStart();
			int limit = req.getLimit();

			Query filterQuery = null;
			DocSet filter = null;
			Filter lfilter = null;

			DocList results = req.getSearcher().getDocList(query, null, sort,
					req.getStart(), req.getLimit(), flags);
			rsp.add(null, results);

			if (qs.startsWith("values")) {
				rsp.add("testname1", "testval1");

				rsp.add("testarr1", new String[] { "my val 1", "my val 2" });

				NamedList nl = new NamedList();
				nl.add("myInt", 333);
				nl.add("myNullVal", null);
				nl.add("myFloat", 1.414213562f);
				nl.add("myDouble", 1e100d);
				nl.add("myBool", false);
				nl.add("myLong", 999999999999L);

				Document doc = new Document();
				doc.add(new Field("id", "55", Field.Store.YES,
						Field.Index.NOT_ANALYZED));
				nl.add("myDoc", doc);

				nl.add("myResult", results);
				nl.add("myStr", "&wow! test escaping: a&b<c&");
				nl.add(null, "this value had a null name...");
				nl.add("myIntArray", new Integer[] { 100, 5, -10, 42 });
				nl.add("epoch", new Date(0));
				nl.add("currDate", new Date(System.currentTimeMillis()));
				rsp.add("myNamedList", nl);
			} else if (qs.startsWith("fields")) {
				NamedList nl = new NamedList();
				Collection<String> flst = new HashSet<String>();
				for (FieldInfo fieldInfo : searcher.getReader().getFieldInfos()) {
					if (fieldInfo.isIndexed) {
						flst.add(fieldInfo.name);
					}
				}
				nl.add("indexed", flst);
				flst = new HashSet<String>();
				for (FieldInfo fieldInfo : searcher.getReader().getFieldInfos()) {
					if (!fieldInfo.isIndexed) {
						flst.add(fieldInfo.name);
					}
				}
				nl.add("unindexed", flst);
				rsp.add("fields", nl);
			}

			test(results.size() <= limit);
			test(results.size() <= results.matches());
			// System.out.println("limit="+limit+" results.size()="+results.size()+" matches="+results.matches());
			test((start == 0 && limit >= results.matches()) ? results.size() == results
					.matches() : true);

			//
			// test against hits
			//
			int numHits;
			ScoreDoc[] scoreDocs;
			if (sort != null) {
				TopFieldDocs hits = searcher.search(query, lfilter, 1000, sort);
				numHits = hits.totalHits;
				scoreDocs = hits.scoreDocs;
			} else {
				TopDocs hits = searcher.search(query, lfilter, 1000);
				numHits = hits.totalHits;
				scoreDocs = hits.scoreDocs;
			}
			test(numHits == results.matches());

			DocList rrr2 = results.subset(start, limit);
			test(rrr2 == results);

			DocIterator iter = results.iterator();

			/***
			 * for (int i=0; i<hits.length(); i++) {
			 * System.out.println("doc="+hits.id(i) + " score="+hits.score(i));
			 * }
			 ***/

			for (int i = 0; i < results.size(); i++) {
				test(iter.nextDoc() == scoreDocs[i].doc);

				// Document doesn't implement equals()
				// test( searcher.document(i).equals(hits.doc(i)));
			}

			DocList results2 = req.getSearcher().getDocList(query, query, sort,
					start, limit);
			test(results2.size() == results.size()
					&& results2.matches() == results.matches());
			DocList results3 = req.getSearcher().getDocList(query, query, null,
					start, limit);
			test(results3.size() == results.size()
					&& results3.matches() == results.matches());

			//
			// getting both the list and set
			//
			DocListAndSet both = searcher.getDocListAndSet(query, filter, sort,
					start, limit);
			test(both.docList.equals(results));
			test(both.docList.matches() == both.docSet.size());
			test((start == 0 && both.docSet.size() <= limit) ? both.docSet
					.equals(both.docList) : true);

			// use the result set as a filter itself...
			DocListAndSet both2 = searcher.getDocListAndSet(query, both.docSet,
					sort, start, limit);
			test(both2.docList.equals(both.docList));
			test(both2.docSet.equals(both.docSet));

			// use the query as a filter itself...
			DocListAndSet both3 = searcher.getDocListAndSet(query, query, sort,
					start, limit);
			test(both3.docList.equals(both.docList));
			test(both3.docSet.equals(both.docSet));

			OpenBitSet bits = both.docSet.getBits();
			OpenBitSet neg = ((OpenBitSet) bits.clone());
			neg.flip(0, bits.capacity());

			// use the negative as a filter (should result in 0 matches)
			// todo - fix if filter is not null
			both2 = searcher.getDocListAndSet(query, new BitDocSet(neg), sort,
					start, limit);
			test(both2.docList.size() == 0);
			test(both2.docList.matches() == 0);
			test(both2.docSet.size() == 0);

			DocSet allResults = searcher.getDocSet(query, filter);
			test(allResults.equals(both.docSet));

			if (filter != null) {
				DocSet res = searcher.getDocSet(query);
				test(res.size() >= results.size());
				test(res.intersection(filter).equals(both.docSet));

				test(res.intersectionSize(filter) == both.docSet.size());
				if (filterQuery != null) {
					test(searcher.numDocs(filterQuery, res) == both.docSet
							.size());
				}
			}

		} catch (Exception e) {
			rsp.setException(e);
			numErrors++;
			return;
		}
	}

	// ////////////////////// SolrInfoMBeans methods //////////////////////

	public String getName() {
		return TestRequestHandler.class.getName();
	}

	public String getVersion() {
		return SolrCore.version;
	}

	public String getDescription() {
		return "A test handler that runs some sanity checks on results";
	}

	public Category getCategory() {
		return Category.QUERYHANDLER;
	}

	public String getSourceId() {
		return "$Id: TestRequestHandler.java 1229268 2012-01-09 17:31:15Z mikemccand $";
	}

	public String getSource() {
		return "$URL: https://svn.apache.org/repos/asf/lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/tst/TestRequestHandler.java $";
	}

	public URL[] getDocs() {
		return null;
	}

	public NamedList getStatistics() {
		NamedList lst = new NamedList();
		lst.add("requests", numRequests);
		lst.add("errors", numErrors);
		return lst;
	}

}
